package org.sean.framework.web.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Whitelist;
import org.sean.framework.util.StringUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * XSS过滤工具类
 *
 * @since 1.0.0
 */
@Slf4j
public class XssUtils extends Whitelist {

    private static final String TAG_ATTR_STYLE = "style";
    private static final String TAG_ATTR_CLASS = "class";

    /**
     * 处理文本
     *
     * @param html 原始数据
     * @return 安全数据
     */
    public static String filter(String html) {
        return Jsoup.clean(html, "", xssWhitelist(), new Document.OutputSettings().prettyPrint(false));
    }

    /**
     * 处理JSON
     *
     * @param json 原始数据
     * @return 安全数据
     */
    public static String filterJson(String json) {
        if (StringUtil.isEmpty(json)) {
            return json;
        }
        try {
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(xssItem(mapper.readValue(json, Object.class)));
        } catch (Exception e) {
            log.warn(e.getMessage(), e);
        }
        return json;
    }

    /**
     * 处理数据
     *
     * @param item 原始数据
     * @return 安全数据
     */
    @SuppressWarnings("unchecked")
    private static Object xssItem(Object item) {
        if (item instanceof Map) {
            return xssMap((Map<String, Object>) item);
        } else if (item instanceof List) {
            return xssList((List<Object>) item);
        } else {
            return xssNormal(item);
        }
    }

    /**
     * 处理MAP
     *
     * @param map 原始数据
     * @return 安全数据
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Object> xssMap(Map<String, Object> map) {
        map.forEach((key, value) -> {
            if (value instanceof Map) {
                xssMap((Map<String, Object>) value);
            } else if (value instanceof List) {
                map.put(key, xssList((List<Object>) (value)));
            } else if (value instanceof String) {
                map.put(key, filter(value.toString()));
            }
        });
        return map;
    }

    /**
     * 处理List
     *
     * @param list 原始数据
     * @return 安全数据
     */
    @SuppressWarnings("unchecked")
    private static List<Object> xssList(List<Object> list) {
        List<Object> newList = new ArrayList<>();
        list.forEach(item -> {
            if (item instanceof Map) {
                xssMap((Map<String, Object>) item);
            } else if (item instanceof List) {
                item = xssList((List<Object>) item);
            } else if (item instanceof String) {
                item = filter((String) item);
            }
            newList.add(item);
        });
        return newList;
    }

    /**
     * 处理普通对象
     *
     * @param item 原始数据
     * @return 安全数据
     */
    private static Object xssNormal(Object item) {
        if (item == null) {
            return null;
        }
        return filter(item.toString());
    }

    /**
     * @return XSS过滤白名单
     */
    private static Whitelist xssWhitelist() {
        return new Whitelist()
                //支持的标签
                .addTags("a", "abbr", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big",
                        "blockquote", "br", "caption", "center", "cite", "code", "col", "colgroup", "dd", "del",
                        "details", "div", "dl", "dt", "em", "embed", "font", "footer", "h1", "h2", "h3", "h4", "h5",
                        "h6", "header", "hr", "i", "img", "ins", "li", "mark", "nav", "ol", "object", "p", "param",
                        "pre", "s", "section", "small", "q", "span", "sub", "sup", "strong", "table", "strike", "tbody",
                        "td", "tfoot", "th", "thead", "tr", "tt", "u", "ul", "video")

                //支持的标签属性
                .addAttributes("a", "href", TAG_ATTR_CLASS, TAG_ATTR_STYLE, "target", "rel", "nofollow", "title")
                .addAttributes("abbr", "title", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("address", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("area", "shape", "coords", "href", "alt")
                .addAttributes("audio", "autoplay", "controls", "loop", "preload", "src", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("b", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("bdi", "dir")
                .addAttributes("bdo", "dir")
                .addAttributes("blockquote", "cite", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("caption", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("code", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("col", "span", "width", "align", "valign", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("colgroup", "span", "width", "align", "valign", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("dd", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("del", "datetime")
                .addAttributes("details", "open")
                .addAttributes("div", TAG_ATTR_CLASS, TAG_ATTR_STYLE, "id")
                .addAttributes("dl", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("dt", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("em", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("embed", "src", "wmode", "flashvars", "pluginspage", "allowFullScreen",
                        "allowfullscreen", "quality", "width", "height", "align", "allowScriptAccess",
                        "allowscriptaccess", "allownetworking", "type")
                .addAttributes("font", "color", "size", "face")
                .addAttributes("h1", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("h2", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("h3", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("h4", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("h5", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("h6", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("hr", TAG_ATTR_CLASS)
                .addAttributes("i", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("img", "align", "alt", "height", "src", "title", "width", TAG_ATTR_CLASS,
                        TAG_ATTR_STYLE, "id", "_src", "loadingclass", "data-latex")
                .addAttributes("ins", "datetime")
                .addAttributes("li", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("ol", "start", "type")
                .addAttributes("object", "type", "id", "name", "data", "width", "height", TAG_ATTR_STYLE,
                        "classid", "codebase")
                .addAttributes("p", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("pre", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("param", "name", "value")
                .addAttributes("span", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("sub", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("sup", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("strong", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("table", "summary", "width", TAG_ATTR_CLASS, TAG_ATTR_STYLE, "border", "align", "valign")
                .addAttributes("tbody", TAG_ATTR_CLASS, TAG_ATTR_STYLE, "align", "valign")
                .addAttributes("td", "abbr", "axis", "colspan", "rowspan", "width", TAG_ATTR_STYLE, "align",
                        "valign", TAG_ATTR_CLASS)
                .addAttributes("tfoot", "align", "valign", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("th", "abbr", "axis", "colspan", "rowspan", "scope", "width", TAG_ATTR_STYLE,
                        "align", "valign", TAG_ATTR_CLASS)
                .addAttributes("thead", "align", "valign", TAG_ATTR_CLASS, TAG_ATTR_STYLE)
                .addAttributes("tr", "abbr", "axis", "colspan", "rowspan", "width", TAG_ATTR_STYLE, "align",
                        "valign", TAG_ATTR_CLASS)
                .addAttributes("q", "cite")
                .addAttributes("ul", "type", TAG_ATTR_STYLE, TAG_ATTR_CLASS)
                .addAttributes("video", "autoplay", "controls", "loop", "preload", "src", "height",
                        "width", TAG_ATTR_CLASS, TAG_ATTR_STYLE)

                //标签属性对应的协议
                .addProtocols("a", "href", "ftp", "http", "https", "mailto")
                .addProtocols("img", "src", "http", "https")
                .addProtocols("blockquote", "cite", "http", "https")
                .addProtocols("cite", "cite", "http", "https")
                .addProtocols("q", "cite", "http", "https")
                .addProtocols("embed", "src", "http", "https");
    }

}
